package fr.generali.sas.dao;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Query;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.collect.Lists;

import fr.generali.sas.domain.DomainViolationException;
import fr.generali.sas.domain.FluxHibernate;
import fr.generali.sas.model.v1_0.Flux;

@Component("fluxHibernateDao")
public class FluxHibernateDao implements InitializingBean, FluxInternalDao {

	EntityManager em;

	@Value("${row.limit}")
	int rowLimit = 100;

	static private Validator validator;
	
	private static Logger logger = LoggerFactory.getLogger(FluxHibernateDao.class);

	/**
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	public void afterPropertiesSet() throws Exception {

		logger.info("SET...");
	}
	
	
	
	@PersistenceContext(type = PersistenceContextType.TRANSACTION)
	public void setEm(EntityManager em) {
		this.em = em;
	}

	/* (non-Javadoc)
	 * @see fr.generali.sas.dao.FluxInternalDao#reserveFluxPourSynchroAgence(java.lang.String, java.lang.String)
	 */
	//@Transactional
	public int reserveFluxPourSynchroAgence(String codeIntermediaire,
			String numeroReservation) {
//		String sql = "update FLUX set NUM_REQUEST = :numReservation where MODEENVOI is null and NUM_REQUEST is null and CDEINTERMEDIAIRE = :codeIntermediaire";
//		Query q = em.createNativeQuery(sql);
		String sql = "update fr.generali.sas.domain.Flux set numReservation = :numReservation, dateEnvoi = sysdate where modeEnvoi is null and numReservation is null and codeIntermediaire = :codeIntermediaire" ;
		Query q = em.createQuery(sql); 
		q.setParameter("numReservation", numeroReservation);
		q.setParameter("codeIntermediaire", codeIntermediaire);
		int updates = q.executeUpdate();
		return updates;
	}
	
	/**{@inheritDoc}
	 * @see fr.generali.sas.dao.FluxInternalDao#reserveFluxPourSynchroClient(java.lang.String, java.lang.String, java.lang.String)
	 */
	//@Transactional(propagation=Propagation.MANDATORY)
	public int reserveFluxPourSynchroClient(String codeIntermediaire,
			String numeroReservation, String idClient) {
		
		String sql = "update fr.generali.sas.domain.Flux set numReservation = :numReservation, dateEnvoi = sysdate where modeEnvoi is null and numReservation is null and codeIntermediaire = :codeIntermediaire and idClient = :idClient" ;
		Query q = em.createQuery(sql); 
		q.setParameter("numReservation", numeroReservation);
		q.setParameter("codeIntermediaire", codeIntermediaire);
		q.setParameter("idClient", idClient);
		int updates = q.executeUpdate();
		return updates;
	}
	

	/**{@inheritDoc}
	 * @see fr.generali.sas.dao.FluxInternalDao#getFluxPourSynchroClientWithRowLimit(java.lang.String, java.lang.String, int)
	 */
	//@Transactional(propagation=Propagation.SUPPORTS, readOnly=true)
	public List<Flux> getFluxPourSynchroClientWithRowLimit(
			String codeIntermediaire, String numeroReservation, int rowLimit) {		
		List<Flux> listFlux = getFluxPourSynchroWithRowLimit(codeIntermediaire,
				numeroReservation, rowLimit);
		return listFlux;
	}
	
	/**{@inheritDoc}
	 * @see fr.generali.sas.dao.FluxInternalDao#getFluxPourSynchroAgenceWithRowLimit(java.lang.String, java.lang.String, int)
	 */
	public List<Flux> getFluxPourSynchroAgenceWithRowLimit(
			String codeIntermediaire, String numeroReservation, int rowLimit) {
		List<Flux> listFlux = getFluxPourSynchroWithRowLimit(codeIntermediaire,
				numeroReservation, rowLimit);
		return listFlux;
	}
	
	/**
	 * @param codeIntermediaire
	 * @param numeroReservation
	 * @param rowLimit
	 * @return
	 */
	private List<Flux> getFluxPourSynchroWithRowLimit(String codeIntermediaire,
			String numeroReservation, int rowLimit) {
		List<Flux> listFlux = Collections.<Flux>emptyList();
		//String sql = "select IDCLIENT, CONTENT, DATEINSERTION, TYPEFLUX, ID from (select IDCLIENT,CONTENT,DATEINSERTION,TYPEFLUX,ID from FLUX where NUM_REQUEST = :numeroReservation and CDEINTERMEDIAIRE = :codeIntermediaire order by ID) t where rownum < :rowLimit ";
//		String sql = "from FLUX where numReservation = :numeroReservation and codeIntermediaire = :codeIntermediaire order by ID ";
//		Map<String, String> args = new HashMap<String, String>();
//		args.put("numeroReservation", numeroReservation);
//		args.put("codeIntermediaire", codeIntermediaire);
//		args.put("rowLimit", String.valueOf(rowLimit));
//		em.createNativeQuery(sql).getResultList();
		
//		CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
//		CriteriaQuery<FluxHibernate> criteria = criteriaBuilder
//				.createQuery(FluxHibernate.class);
//		Root<FluxHibernate> fluxRoot = criteria.from(FluxHibernate.class);
//		criteria.select(fluxRoot);
//		Predicate predicateNumRequest = criteriaBuilder.equal(fluxRoot.<String> get("numReservation").get("numReservation"),criteriaBuilder.parameter(String.class, "numeroReservation"));
//		//Predicate predicateNumRequest = criteriaBuilder.equal(fluxRoot.<String> get("numReservation"),criteriaBuilder.parameter(RequestHibernate.class, "numeroReservation"));
//		Predicate predicateCodeIntermediaire = criteriaBuilder.equal(fluxRoot.<String> get("codeIntermediaire"),criteriaBuilder.parameter(String.class, "codeIntermediaire"));
//		criteria.where(criteriaBuilder.and(predicateNumRequest, predicateCodeIntermediaire));
//		
//		TypedQuery<FluxHibernate> tq = em.createQuery(criteria);
//		tq.setMaxResults(rowLimit);
//		tq.setParameter("codeIntermediaire", codeIntermediaire);
//		RequestHibernate req = new RequestHibernate();
//		req.setNumRequest(numeroReservation);
//		//tq.setParameter("numeroReservation", req);	
//		tq.setParameter("numeroReservation", numeroReservation);
//		List<FluxHibernate> listFluxHibernate = em.createQuery( criteria ).getResultList();
		
		//String sql = "select IDCLIENT, CONTENT, DATEINSERTION, TYPEFLUX, ID from (select IDCLIENT,CONTENT,DATEINSERTION,TYPEFLUX,ID from FLUX where NUM_REQUEST = :numeroReservation and CDEINTERMEDIAIRE = :codeIntermediaire order by ID) t where rownum < :rowLimit ";
		//String sql = "select IDCLIENT,CDEINTERMEDIAIRE,CONTENT,DATEINSERTION,TYPEFLUX,ID from FLUX where NUM_REQUEST = :numeroReservation and CDEINTERMEDIAIRE = :codeIntermediaire order by ID";
		//Query q = em.createNativeQuery(sql, FluxHibernate.class);
		String sql = "from fr.generali.sas.domain.Flux where numReservation = :numeroReservation and codeIntermediaire = :codeIntermediaire order by ID";
		Query q = em.createQuery(sql);
		q.setMaxResults(rowLimit);
		//Map<String, String> args = new HashMap<String, String>();
		q.setParameter("numeroReservation", numeroReservation);
		q.setParameter("codeIntermediaire", codeIntermediaire);
		
		@SuppressWarnings("unchecked")
		List<FluxHibernate> listFluxHibernate = q.getResultList();
		
		listFlux = Lists.transform(listFluxHibernate, new FluxFunction());

		return listFlux;
	}

	/**{@inheritDoc}
	 * @see fr.generali.sas.dao.FluxInternalDao#getNextFluxPourSynchroClientWithRowLimit(java.lang.String, java.lang.String, int, int)
	 */
	public List<Flux> getNextFluxPourSynchroClientWithRowLimit(
			String codeIntermediaire, String numeroReservation, int rowLimit,
			int fromId) {	
		List<Flux> listFlux = Collections.<Flux>emptyList();
		listFlux = getNextFluxPourSynchroWithRowLimit(codeIntermediaire,numeroReservation, rowLimit, fromId);
		return listFlux;
	}
	
	/**{@inheritDoc}
	 * @see fr.generali.sas.dao.FluxInternalDao#getNextFluxPourSynchroAgenceWithRowLimit(java.lang.String, java.lang.String, int, int)
	 */
	public List<Flux> getNextFluxPourSynchroAgenceWithRowLimit(
			String codeIntermediaire, String numeroReservation, int rowLimit,
			int fromId) {
		List<Flux> listFlux = Collections.<Flux> emptyList();

		listFlux = getNextFluxPourSynchroWithRowLimit(codeIntermediaire,numeroReservation, rowLimit, fromId);
		return listFlux;
	}

	/**
	 * @param codeIntermediaire
	 * @param numeroReservation
	 * @param rowLimit
	 * @param fromId
	 * @return
	 */
	private List<Flux> getNextFluxPourSynchroWithRowLimit(
			String codeIntermediaire, String numeroReservation, int rowLimit,
			int fromId) {
		List<Flux> listFlux = Collections.<Flux>emptyList();
		String sql = "from fr.generali.sas.domain.Flux where numReservation = :numeroReservation and codeIntermediaire = :codeIntermediaire and id > :fromId order by ID";
		Query q = em.createQuery(sql);
		q.setMaxResults(rowLimit);
		q.setParameter("numeroReservation", numeroReservation);
		q.setParameter("codeIntermediaire", codeIntermediaire);
		q.setParameter("fromId", Long.valueOf(fromId));
		
		@SuppressWarnings("unchecked")
		List<FluxHibernate> listFluxHibernate = q.getResultList();
		
		listFlux = Lists.transform(listFluxHibernate, new FluxFunction());
		return listFlux;
	}

	
	/**{@inheritDoc}
	 * @see fr.generali.sas.dao.FluxInternalDao#getFluxPourSynchroClient(java.lang.String, java.lang.String)
	 */
	//@Transactional(propagation=Propagation.SUPPORTS, readOnly=true)
	public List<Flux> getFluxPourSynchroClient(String codeIntermediaire,
			String numeroReservation) {
		List<Flux> lstFlux = getFLuxPourSynchro(codeIntermediaire,numeroReservation);

		return lstFlux;
	}

	/**{@inheritDoc}
	 * @see fr.generali.sas.dao.FluxInternalDao#getFluxPourSynchroAgence(java.lang.String, java.lang.String)
	 */
	public List<Flux> getFluxPourSynchroAgence(String codeIntermediaire,
			String numeroReservation) {
		List<Flux> lstFlux = getFLuxPourSynchro(codeIntermediaire,
				numeroReservation);

		return lstFlux;
	}

	/**
	 * Retourne la liste des flux reservés lors d'une demande de synchro
	 * 
	 * @param codeIntermediaire
	 *            code de l'intermediaire
	 * @param numeroReservation
	 *            identifiant de la demande de flux
	 * @return
	 */
	private List<Flux> getFLuxPourSynchro(String codeIntermediaire,
			String numeroReservation) {
		List<Flux> listFlux = Collections.<Flux>emptyList();
		String sql = "from fr.generali.sas.domain.Flux where numReservation = :numeroReservation and codeIntermediaire = :codeIntermediaire order by ID";

		Query q = em.createQuery(sql);
		q.setParameter("numeroReservation", numeroReservation);
		q.setParameter("codeIntermediaire", codeIntermediaire);

		@SuppressWarnings("unchecked")
		List<FluxHibernate> listFluxHibernate = q.getResultList();
		
		listFlux = Lists.transform(listFluxHibernate, new FluxFunction());
		return listFlux;
	}

	/**
	 * @param codeIntermediaire
	 * @param numeroReservation
	 * @param statut
	 * @return
	 */
	@Transactional
	public int acquitteIntegrationFlux(String codeIntermediaire,
			String numeroReservation, boolean statut) {

		if (statut)
			return acquitteSuccesIntegrationFlux(codeIntermediaire,
					numeroReservation);
		else
			return acquitteEchecIntegrationFlux(codeIntermediaire,
					numeroReservation);
	}
	
	/**
	 * Lors d'un succes d'intégration, valide la reservation des flux avec la date courante et le mode d'envoia TP (par opposition au mode batch [utile pour le HG juin 2011])
	 * 
	 * @param codeIntermediaire
	 *            code de l'intermediaire
	 * @param numeroReservation
	 *            identifiant de la demande de flux
	 * @return
	 */
	private int acquitteSuccesIntegrationFlux(String codeIntermediaire,
			String numeroReservation) {
	
		String sql ="update fr.generali.sas.domain.Flux set modeEnvoi = 'TP' , dateAck = sysdate where numReservation = :numeroReservation and CDEINTERMEDIAIRE = :codeIntermediaire";	
		Query q = em.createQuery(sql); 
		q.setParameter("numeroReservation", numeroReservation);
		q.setParameter("codeIntermediaire", codeIntermediaire);
		int updates = q.executeUpdate();
		return updates;
		
	}
	
	/**
	 * Lors d'un echec d'intégration, supprime la reservation des flux pour autoriser le recyclage
	 * 
	* @param codeIntermediaire
	 *            code de l'intermediaire
	 * @param numeroReservation
	 *            identifiant de la demande de flux
	 * @return
	 */
	private int acquitteEchecIntegrationFlux(String codeIntermediaire,
			String numeroReservation) {
		String sql = "update fr.generali.sas.domain.Flux set numReservation = NULL, dateEnvoi = NULL where numReservation = :numeroReservation and codeIntermediaire = :codeIntermediaire";
		Query q = em.createQuery(sql); 
		q.setParameter("numeroReservation", numeroReservation);
		q.setParameter("codeIntermediaire", codeIntermediaire);
		int updates = q.executeUpdate();
		return updates;
	}

	/**{@inheritDoc}
	 * @see fr.generali.sas.dao.FluxInternalDao#getNbFluxForReservation(java.lang.String, java.lang.String)
	 */
	public int getNbFluxForReservation(String codeIntermediaire,
			String numeroReservation) {
		
		String sql = "select count(F.id) from fr.generali.sas.domain.Flux F where numReservation = :numeroReservation and codeIntermediaire = :codeIntermediaire ";
		Query q = em.createQuery(sql); 
		q.setParameter("numeroReservation", numeroReservation);
		q.setParameter("codeIntermediaire", codeIntermediaire);
		Long nbFlux = (Long) q.getSingleResult();

		return nbFlux.intValue();
	}



	/**{@inheritDoc}
	 * @see fr.generali.sas.dao.FluxInternalDao#save(fr.generali.sas.domain.FluxHibernate)
	 */
	public FluxHibernate save(FluxHibernate f) throws Exception {

		ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
		validator = factory.getValidator();
		Set<ConstraintViolation<FluxHibernate>> constraintViolations = validator.validate(f);
		if (constraintViolations.isEmpty()) {
			em.persist(f);
		} else {
			StringBuilder sb = new StringBuilder();
			Iterator<ConstraintViolation<FluxHibernate>> it = constraintViolations.iterator();
			while (it.hasNext()) {
				ConstraintViolation<FluxHibernate> violation = it.next();
				sb.append("\n Message:").append(violation.getMessage()).append("(Attribut:  ").append(violation.getPropertyPath()).append("   valeur: ")
						.append(violation.getInvalidValue()).append(")  ");
			}
			Exception ex = new DomainViolationException(sb.toString());
			throw ex;
		}

		return f;
	}

}
